⚡ 30 秒速记
- 工作单元把遍历进度从调用栈移到数据结构
- 调度按优先级选择工作和让出时机
- 渲染可重试,提交应用一致结果
- 中断不等于另开线程,也不打断任意用户函数
- 减少总工作与改善调度要同时考虑
Fiber 让界面更新不必一口气递归到底,而是把工作进度保存成可调度的单元。 这样框架有机会先处理重要更新,再继续或重新计算其他工作。可中断的是渲染计算,提交负责把完成结果应用到页面,避免展示半成品。但它不是多线程,也不能在用户函数内部任意抢占,组件里的一大段同步计算仍会阻塞主线程,需要单独优化。
版本校准: 原理文章中的代码代表特定实现与写作时间。应用到当前项目时,应先确认浏览器、框架或工具的主版本,再区分稳定的规范语义、可变化的内部实现和项目自身约束。
在 16.x 版本中将其最为核心的 Diff 算法整个重写,使其以“Fiber Reconciler”的全新面貌示人。
那么 Stack Reconciler 到底有着怎样根深蒂固的局限性,使得 React 不得不从架构层面做出改变?而 Fiber 架构又是何方神圣,基于它来实现的调和过程又有什么不同呢?本讲我们就围绕这两个大问题展开讨论。
# 前置知识:单线程的 JavaScript 与多线程的浏览器
大家在入门前端的时候,想必都听说过这样一个结论:JavaScript 是单线程的,浏览器是多线程的。
对于多线程的浏览器来说,它除了要处理 JavaScript 线程以外,还需要处理包括事件系统、定时器/延时器、网络请求等各种各样的任务线程,这其中,自然也包括负责处理 DOM 的UI 渲染线程。而 JavaScript 线程是可以操作 DOM 的。
这意味着什么呢?试想如果渲染线程和 JavaScript 线程同时在工作,那么渲染结果必然是难以预测的:比如渲染线程刚绘制好的画面,可能转头就会被一段 JavaScript 给改得面目全非。这就决定了JavaScript 线程和渲染线程必须是互斥的:这两个线程不能够穿插执行,必须串行。当其中一个线程执行时,另一个线程只能挂起等待。
具有相似特征的还有事件线程,浏览器的 Event-Loop 机制决定了事件任务是由一个异步队列来维持的。当事件被触发时,对应的任务不会立刻被执行,而是由事件线程把它添加到任务队列的末尾,等待 JavaScript 的同步代码执行完毕后,在空闲的时间里执行出队。
在这样的机制下,若 JavaScript 线程长时间地占用了主线程,那么渲染层面的更新就不得不长时间地等待,界面长时间不更新,带给用户的体验就是所谓的“卡顿”。一般页面卡顿的时候,你会做什么呢?我个人的习惯是更加频繁地在页面上点来点去,期望页面能够给我哪怕一点点的响应。遗憾的是,事件线程也在等待 JavaScript,这就导致你触发的事件也将是难以被响应的。
试想一下界面不更新、交互无反应的这种感觉,是不是非常令人抓狂?这其实正是 Stack Reconciler 后期所面临的困局。